//-----------------------------------------------------------------------------
//
//  pyopenzwave.cpp
//
//  Python wrapper for OpenZWave
//
//  Copyright (c) 2011 Evolui, LLC <evolui1@evolui.org>
//
//  SOFTWARE NOTICE AND LICENSE
//
//  This file is part of py-openzwave-extension.
//
//  py-openzwave-extension is free software: you can redistribute it and/or
//  modify it under the terms of the GNU Lesser General Public License as
//  published by the Free Software Foundation, either version 3 of the License,
//  or (at your option) any later version.
//
//  py-openzwave-extension is distributed in the hope that it will be useful, 
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Lesser General Public License for more details.
//
//  You should have received a copy of the GNU Lesser General Public License
//  along with py-openzwave-extension.  If not, see
//  <http://www.gnu.org/licenses/>.
//
//-----------------------------------------------------------------------------
#include <Python.h>

//#include <unistd.h>
//#include <pthread.h>
#include "Manager.h"
#include "Options.h"
#include "Node.h"
#include "Group.h"
#include "Notification.h"
#include "ValueStore.h"
#include "Value.h"
#include "ValueBool.h"

#include "Alarm.h"
#include "ApplicationStatus.h"
#include "Association.h"
#include "AssociationCommandConfiguration.h"
#include "Basic.h"
#include "BasicWindowCovering.h"
#include "Battery.h"
#include "ClimateControlSchedule.h"
#include "Clock.h"
#include "Configuration.h"
#include "ControllerReplication.h"
#include "EnergyProduction.h"
#include "Hail.h"
#include "Indicator.h"
#include "Language.h"
#include "Lock.h"
#include "ManufacturerSpecific.h"
#include "Meter.h"
#include "MeterPulse.h"
#include "MultiCmd.h"
#include "MultiInstance.h"
#include "MultiInstanceAssociation.h"
#include "NodeNaming.h"
#include "Powerlevel.h"
#include "Proprietary.h"
#include "Protection.h"
#include "SensorAlarm.h"
#include "SensorBinary.h"
#include "SensorMultilevel.h"
#include "SwitchAll.h"
#include "SwitchBinary.h"
#include "SwitchMultilevel.h"
#include "SwitchToggleBinary.h"
#include "SwitchToggleMultilevel.h"
#include "ThermostatFanMode.h"
#include "ThermostatFanState.h"
#include "ThermostatMode.h"
#include "ThermostatOperatingState.h"
#include "ThermostatSetpoint.h"
#include "Version.h"
#include "WakeUp.h"

#include "ValueBool.h"
#include "ValueButton.h"
#include "ValueByte.h"
#include "ValueDecimal.h"
#include "ValueInt.h"
#include "ValueList.h"
#include "ValueSchedule.h"
#include "ValueShort.h"
#include "ValueString.h"

#include "Manager.h"
#include "Options.h"
#include "Utils.h"

using namespace OpenZWave;
static PyObject *OpenZWaveException;

///////////////////////////////////////////////////////////////////////////////
// pyopenzwave

static PyMethodDef pyopenzwave_Methods[] = {


    {NULL, NULL, 0, NULL}        /* Sentinel */
};

static Manager*
get_manager()
{
    return OpenZWave::Manager::Get();
}

static Options*
get_options()
{
    return OpenZWave::Options::Get();
}

// pyopenzwave
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// pyopenzwave.ValueID

typedef struct {
    PyObject_HEAD
    OpenZWave::ValueID valueid;
} PyValueID;

static void
PyValueID_dealloc(PyValueID *self)
{
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
PyValueID_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyValueID *self;
    self = (PyValueID *)type->tp_alloc(type, 0);
    if (self != NULL)
        memset(&self->valueid, 0, sizeof(OpenZWave::ValueID));

    return (PyObject *)self;
}

static int
PyValueID_init(PyValueID *self, PyObject *args, PyObject *kwds)
{
    return 0;
}

static PyObject *
PyValueID_GetType(PyObject *s, PyObject *args)
{
    PyValueID *self = (PyValueID*)s;

    return PyUnicode_FromString(OpenZWave::Value::GetTypeNameFromEnum(self->valueid.GetType()));
}

static PyObject *
PyValueID_GetCommandClassId(PyObject *s, PyObject *args)
{
    PyValueID *self = (PyValueID*)s;

    return PyLong_FromLong((unsigned long)self->valueid.GetCommandClassId());
}

static PyObject *
PyValueID_GetGenre(PyObject *s, PyObject *args)
{
    PyValueID *self = (PyValueID*)s;

    return PyUnicode_FromString(OpenZWave::Value::GetGenreNameFromEnum(self->valueid.GetGenre()));
}

static PyObject *
PyValueID_richcmp(PyObject *left, PyObject *right, int op)
{
    PyObject *result;
    PyValueID *obj1 = (PyValueID*)left, *obj2 = (PyValueID*)right;
    int c = 0;

    switch (op) {
     case Py_LT: c = obj1->valueid <  obj2->valueid; break;
     case Py_LE: c = !(obj1->valueid > obj2->valueid); break;
     case Py_EQ: c = obj1->valueid == obj2->valueid; break;
     case Py_NE: c = obj1->valueid != obj2->valueid; break;
     case Py_GT: c = obj1->valueid >  obj2->valueid; break;
     case Py_GE: c = !(obj1->valueid < obj2->valueid); break;
    }

    result = c ? Py_True : Py_False;

    Py_INCREF(result);
    return result;
}

static PyMethodDef PyValueID_Methods[] = {

    {"GetType",  PyValueID_GetType, METH_VARARGS,
     "Get the type of this ValueID."},

    {"GetGenre",  PyValueID_GetGenre, METH_VARARGS,
     "Get the genre of this ValueID."},

    {"GetCommandClassId",  PyValueID_GetCommandClassId, METH_VARARGS,
     "Get the command class ID of this ValueID."},

    {NULL, NULL, 0, NULL}
};

static PyTypeObject PyValueIDType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "pyopenzwave.ValueID",     /*tp_name*/
    sizeof(PyValueID),         /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    (destructor)PyValueID_dealloc, /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_compare*/
    0,                         /*tp_repr*/
    0,                         /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
    "ValueID wrapper",         /* tp_doc */
    0,                         /* tp_traverse */
    0,                         /* tp_clear */
    PyValueID_richcmp,         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    PyValueID_Methods,         /* tp_methods */
    0,                         /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)PyValueID_init,  /* tp_init */
    0,                         /* tp_alloc */
    PyValueID_new,             /* tp_new */
};

// pyopenzwave.ValueID
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// pyopenzwave.Driver

typedef struct {
    PyObject_HEAD
    OpenZWave::Driver driver;
} PyDriver;

static void
PyDriver_dealloc(PyDriver *self)
{
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
PyDriver_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyDriver *self;
    self = (PyDriver *)type->tp_alloc(type, 0);
    if (self != NULL)
        memset(&self->driver, 0, sizeof(OpenZWave::Driver));

    return (PyObject *)self;
}

static int
PyDriver_init(PyDriver *self, PyObject *args, PyObject *kwds)
{
    return 0;
}

static PyTypeObject PyDriverType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "pyopenzwave.Driver",     /*tp_name*/
    sizeof(PyDriver),         /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    (destructor)PyDriver_dealloc, /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_compare*/
    0,                         /*tp_repr*/
    0,                         /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
    "Driver wrapper",         /* tp_doc */
    0,                         /* tp_traverse */
    0,                         /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    0,                         /* tp_methods */
    0,                         /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)PyDriver_init,  /* tp_init */
    0,                         /* tp_alloc */
    PyDriver_new,             /* tp_new */
};

// pyopenzwave.Driver
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// pyopenzwave.Notification

typedef struct {
    PyObject_HEAD
    OpenZWave::Notification notification;
} PyNotification;

static void
PyNotification_dealloc(PyNotification *self)
{
    Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyObject *
PyNotification_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    PyNotification *self;
    self = (PyNotification *)type->tp_alloc(type, 0);
    if (self != NULL)
        memset(&self->notification, 0, sizeof(OpenZWave::Notification));

    return (PyObject *)self;
}

static int
PyNotification_init(PyNotification *self, PyObject *args, PyObject *kwds)
{
    return 0;
}

static PyObject *
PyNotification_GetType(PyObject *s, PyObject *args)
{
    PyNotification *self = (PyNotification*)s;

    return PyLong_FromLong((unsigned long)self->notification.GetType());
}

static PyObject *
PyNotification_GetHomeId(PyObject *s, PyObject *args)
{
    PyNotification *self = (PyNotification*)s;

    return PyLong_FromLong((unsigned long)self->notification.GetHomeId());
}

static PyObject *
PyNotification_GetNodeId(PyObject *s, PyObject *args)
{
    PyNotification *self = (PyNotification*)s;

    return PyLong_FromLong((unsigned long)self->notification.GetNodeId());
}

static PyObject *
PyNotification_GetValueID(PyObject *s, PyObject *args)
{
    PyNotification *self = (PyNotification*)s;
    PyValueID *valueid = (PyValueID*)PyValueID_new(&PyValueIDType, NULL, NULL);
    valueid->valueid = self->notification.GetValueID();

    return (PyObject*)valueid;
}

static PyObject *
PyNotification_GetGroupIdx(PyObject *s, PyObject *args)
{
    PyNotification *self = (PyNotification*)s;

    return PyLong_FromLong((unsigned long)self->notification.GetGroupIdx());
}

static PyObject *
PyNotification_GetEvent(PyObject *s, PyObject *args)
{
    PyNotification *self = (PyNotification*)s;

    return PyLong_FromLong((unsigned long)self->notification.GetEvent());
}

static PyObject *
PyNotification_GetByte(PyObject *s, PyObject *args)
{
    PyNotification *self = (PyNotification*)s;

    return PyLong_FromLong((unsigned long)self->notification.GetByte());
}

static PyMethodDef PyNotification_Methods[] = {

    {"GetType",  PyNotification_GetType, METH_VARARGS,
     "Get the type of this notification."},

    {"GetHomeId",  PyNotification_GetHomeId, METH_VARARGS,
     "Get the Home ID of the driver sending this notification."},

    {"GetNodeId",  PyNotification_GetNodeId, METH_VARARGS,
     "Get the ID of any node involved in this notification."},

    {"GetValueID",  PyNotification_GetValueID, METH_VARARGS,
     "Get the unique ValueID of any value involved in this notification."},

    {"GetGroupIdx",  PyNotification_GetGroupIdx, METH_VARARGS,
     "Get the index of the association group that has been changed.  Only valid in NotificationType::Type_Group notifications."},

    {"GetEvent",  PyNotification_GetEvent, METH_VARARGS,
     "Get the event value of a node.  Only valid in NotificationType::Type_NodeEvent notifications."},

    {"GetByte",  PyNotification_GetByte, METH_VARARGS,
     "Helper function to simplify wrapping the notification class.  Should not normally need to be called."},

    {NULL, NULL, 0, NULL}
};

static PyTypeObject PyNotificationType = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "pyopenzwave.Notification",     /*tp_name*/
    sizeof(PyNotification),         /*tp_basicsize*/
    0,                         /*tp_itemsize*/
    (destructor)PyNotification_dealloc, /*tp_dealloc*/
    0,                         /*tp_print*/
    0,                         /*tp_getattr*/
    0,                         /*tp_setattr*/
    0,                         /*tp_compare*/
    0,                         /*tp_repr*/
    0,                         /*tp_as_number*/
    0,                         /*tp_as_sequence*/
    0,                         /*tp_as_mapping*/
    0,                         /*tp_hash */
    0,                         /*tp_call*/
    0,                         /*tp_str*/
    0,                         /*tp_getattro*/
    0,                         /*tp_setattro*/
    0,                         /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
    "Notification wrapper",         /* tp_doc */
    0,                         /* tp_traverse */
    0,                         /* tp_clear */
    0,                         /* tp_richcompare */
    0,                         /* tp_weaklistoffset */
    0,                         /* tp_iter */
    0,                         /* tp_iternext */
    PyNotification_Methods,  /* tp_methods */
    0,                         /* tp_members */
    0,                         /* tp_getset */
    0,                         /* tp_base */
    0,                         /* tp_dict */
    0,                         /* tp_descr_get */
    0,                         /* tp_descr_set */
    0,                         /* tp_dictoffset */
    (initproc)PyNotification_init,  /* tp_init */
    0,                         /* tp_alloc */
    PyNotification_new,             /* tp_new */
};

// pyopenzwave.Notification
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// pyopenzwave.options

static void dumpclasses()
{
    printf("%d, %s\n", Alarm::StaticGetCommandClassId(), Alarm::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", ApplicationStatus::StaticGetCommandClassId(), ApplicationStatus::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", Association::StaticGetCommandClassId(), Association::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", AssociationCommandConfiguration::StaticGetCommandClassId(), AssociationCommandConfiguration::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", Basic::StaticGetCommandClassId(), Basic::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", BasicWindowCovering::StaticGetCommandClassId(), BasicWindowCovering::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", Battery::StaticGetCommandClassId(), Battery::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", ClimateControlSchedule::StaticGetCommandClassId(), ClimateControlSchedule::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", Clock::StaticGetCommandClassId(), Clock::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", Configuration::StaticGetCommandClassId(), Configuration::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", ControllerReplication::StaticGetCommandClassId(), ControllerReplication::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", EnergyProduction::StaticGetCommandClassId(), EnergyProduction::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", Hail::StaticGetCommandClassId(), Hail::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", Indicator::StaticGetCommandClassId(), Indicator::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", Language::StaticGetCommandClassId(), Language::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", Lock::StaticGetCommandClassId(), Lock::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", ManufacturerSpecific::StaticGetCommandClassId(), ManufacturerSpecific::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", Meter::StaticGetCommandClassId(), Meter::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", MeterPulse::StaticGetCommandClassId(), MeterPulse::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", MultiCmd::StaticGetCommandClassId(), MultiCmd::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", MultiInstance::StaticGetCommandClassId(), MultiInstance::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", MultiInstanceAssociation::StaticGetCommandClassId(), MultiInstanceAssociation::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", NodeNaming::StaticGetCommandClassId(), NodeNaming::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", Powerlevel::StaticGetCommandClassId(), Powerlevel::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", Proprietary::StaticGetCommandClassId(), Proprietary::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", Protection::StaticGetCommandClassId(), Protection::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", SensorAlarm::StaticGetCommandClassId(), SensorAlarm::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", SensorBinary::StaticGetCommandClassId(), SensorBinary::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", SensorMultilevel::StaticGetCommandClassId(), SensorMultilevel::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", SwitchAll::StaticGetCommandClassId(), SwitchAll::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", SwitchBinary::StaticGetCommandClassId(), SwitchBinary::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", SwitchMultilevel::StaticGetCommandClassId(), SwitchMultilevel::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", SwitchToggleBinary::StaticGetCommandClassId(), SwitchToggleBinary::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", SwitchToggleMultilevel::StaticGetCommandClassId(), SwitchToggleMultilevel::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", ThermostatFanMode::StaticGetCommandClassId(), ThermostatFanMode::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", ThermostatFanState::StaticGetCommandClassId(), ThermostatFanState::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", ThermostatMode::StaticGetCommandClassId(), ThermostatMode::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", ThermostatOperatingState::StaticGetCommandClassId(), ThermostatOperatingState::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", ThermostatSetpoint::StaticGetCommandClassId(), ThermostatSetpoint::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", Version::StaticGetCommandClassId(), Version::StaticGetCommandClassName().c_str());
    printf("%d, %s\n", WakeUp::StaticGetCommandClassId(), WakeUp::StaticGetCommandClassName().c_str());
}

static PyObject *
options_Create(PyObject *self, PyObject *args)
{
    const char *_configPath, *_userPath, *_commandLine;

    if (!PyArg_ParseTuple(args, "sss", &_configPath, &_userPath, &_commandLine))
        return NULL;

    OpenZWave::Options::Create(string(_configPath), string(_userPath), string(_commandLine));

    //dumpclasses();
    Py_RETURN_NONE;
}

static PyObject *
options_Lock(PyObject *self, PyObject *args)
{
    bool ret;

    ret = get_options()->Lock();

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
options_AreLocked(PyObject *self, PyObject *args)
{
    bool ret;

    ret = get_options()->AreLocked();

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyMethodDef options_Methods[] = {

    {"Create",  options_Create, METH_VARARGS,
     "Instantiate built-in singleton Options class."},

    {"Lock",  options_Lock, METH_VARARGS,
     "Locks the options."},

    {"AreLocked",  options_AreLocked, METH_VARARGS,
     "Test whether the options have been locked."},

    {NULL, NULL, 0, NULL}
};

// pyopenzwave.options
///////////////////////////////////////////////////////////////////////////////

///////////////////////////////////////////////////////////////////////////////
// pyopenzwave.manager
typedef struct {
    PyObject *func;
    PyObject *args;
    PyObject *kwargs;
    uint32 _homeId;
} PyCallbackObjects;

list<PyCallbackObjects*> watchers;
map<void*, PyCallbackObjects*> ongoing_commands;

void OnControllerCommand
(
    Driver::ControllerState _state,
    void* _context
)
{
    
    //printf("entering callback\n");fflush(stdout);
    
    if(ongoing_commands.count(_context) < 1)
    {
        //printf("zwave calling controller callback after command has been completed.  callback objects have already been cleaned up so no callback will be performed.\n");fflush(stdout);
        
        return;
    }
    
    /*
     * obtain global interpreter lock (PIL).  this is very necessary because
     * this routine is being called from a thread that is not being driven by
     * the main python thread.  python only allows one thread to talk to it at
     * a time, so we need to request a lock before we do so, since this is an
     * unfamiliar thread.
     */
    //printf("*** OnControllerCommand::obtaining lock obtained for state %d\n", (int)_state);fflush(stdout);
    
    PyGILState_STATE gstate;
    gstate = PyGILState_Ensure();

    //printf("*** OnControllerCommand::lock obtained for state %d\n", (int)_state);fflush(stdout);

    PyCallbackObjects *callback_objects = (PyCallbackObjects*)_context;
    PyObject *func, *args, *kwargs, *result = NULL, *args_with_state, *pystate;
    //printf("OnControllerCommand: %8X\n", (unsigned long)_state);fflush(stdout);
    func = callback_objects->func;
    args = callback_objects->args;
    kwargs = callback_objects->kwargs;

    /*
    printf("callback objects retrieved\n");fflush(stdout);

    fflush(stdout);printf("\nstate: %8X - ", (unsigned long)_state);fflush(stdout);
    PyObject_Print(pystate, stdout, 0); fflush(stdout);

    fflush(stdout);printf("\nfunc: %8X - ", (unsigned long)func);fflush(stdout);
    PyObject_Print(func, stdout, 0); fflush(stdout);

    fflush(stdout);printf("\nargs: %8X - ", (unsigned long)args);fflush(stdout);
    PyObject_Print(args, stdout, 0); fflush(stdout);

    fflush(stdout);printf("\nkwargs: %8X - ", (unsigned long)kwargs);fflush(stdout);
    PyObject_Print(kwargs, stdout, 0); fflush(stdout);
    */
    
    // create a new args tuple with notification as first argument
    args_with_state = PyTuple_New(PyTuple_Size(args) + 1);
    pystate = PyLong_FromLong((unsigned long) _state);
    PyTuple_SetItem(args_with_state, 0, pystate);
    for(unsigned int i = 0; i < PyTuple_Size(args); i++)
    {
        PyObject *ref = PyTuple_GetItem(args, i);
        // there is no need to Py_INCREF(ref) because the reference count is
        // already 1+ since we are referencing it with the args tuple. we plan
        // to schedule args_with_state for deletion after the callback
        // function is completed
        PyTuple_SetItem(args_with_state, i+1, ref);
    }

    //printf("\narguments created\n");fflush(stdout);

    if(kwargs != NULL)
        result = PyObject_Call(func, args_with_state, kwargs);
    else
        result = PyObject_CallObject(func, args_with_state);

    //printf("callback executed\n");fflush(stdout);

    // callback is complete.  cleanup.
    Py_DECREF(args_with_state);
    Py_DECREF(pystate);

    //printf("temp variables cleaned up\n");fflush(stdout);

    //
    // only clean up func, args, kwargs, and callback_objects when the command
    // command is complete.  otherwise, there are multiple phases to
    // this process and these should be preserved until the transaction is
    // complete.  in the failed case, a command should be cancelled explicitly
    // in which case the completed notification will be sent afterward.
    //
    if (_state == Driver::ControllerState_Completed)
    {
        // VERY IMPORTANT: callback_objects are stored in the driver class
        // under a protected variable.  any internal call to the controller
        // will result in the callback using the callback objects.  we are
        // deleting them here, but they will still be stored by zwave internally
        // and in some cases (such as adding a node) zwave will still call
        // back this routine after the protocol has supposedly been completed
        // (i.e. with a neighbor node update
        ongoing_commands.erase(_context);
        //
        // this was the original thought to clear out the internal variables but
        // zwave exits immediately if the last command is still "going" which
        // apparently it is even though this routine is still being called with
        // "complete"
        //        
        //bool ret = get_manager()->BeginControllerCommand(callback_objects->_homeId, Driver::ControllerCommand_None);
        
        Py_DECREF(func);
        Py_DECREF(args);
        Py_XDECREF(kwargs); // could be NULL, so XDECREF it
        delete callback_objects;

        //printf("transaction complete\n");fflush(stdout);

    }

    if (result == NULL)
        PyErr_PrintEx(0);
    else
        Py_DECREF(result);

    //printf("return value cleared\n");fflush(stdout);

    /*
     * let python know this thread is done modifying and calling it's objects
     */
    PyGILState_Release(gstate);

    //printf("*** OnControllerCommand::lock released\n");fflush(stdout);

}
/*
 * all watchers using a python wrapper will be proxied through this function
 * where _context will be converted to a python func, args, and kwargs and
 * then executed.
 */
void OnNotification
(
    OpenZWave::Notification const* _notification,
    void* _context
)
{
    //printf("*** OnNotification::obtaining lock obtained\n");fflush(stdout);
    

    /*
     * obtain global interpreter lock (PIL).  this is very necessary because
     * this routine is being called from a thread that is not being driven by
     * the main python thread.  python only allows one thread to talk to it at
     * a time, so we need to request a lock before we do so, since this is an
     * unfamiliar thread.
     */
    PyGILState_STATE gstate;
    gstate = PyGILState_Ensure();

    //printf("*** OnNotification::lock obtained\n");fflush(stdout);
    //printf("processing notification:\n");fflush(stdout);
    
    PyCallbackObjects *callback_objects = (PyCallbackObjects*)_context;
    PyObject *func, *args, *kwargs, *result = NULL, *args_with_notification;
    PyNotification *pynotification = (PyNotification*)PyNotification_new((PyTypeObject*)&PyNotificationType, NULL, NULL);
    func = callback_objects->func;
    args = callback_objects->args;
    kwargs = callback_objects->kwargs;

    // create a new args tuple with notification as first argument
    args_with_notification = PyTuple_New(PyTuple_Size(args) + 1);
    if(_notification != NULL)
        pynotification->notification = *_notification;
    Py_INCREF(pynotification);
    PyTuple_SetItem(args_with_notification, 0, (PyObject*)pynotification);
    for(unsigned int i = 0; i < PyTuple_Size(args); i++)
    {
        PyObject *ref = PyTuple_GetItem(args, i);
        // there is no need to Py_INCREF(ref) because the reference count is
        // already 1+ since we are referencing it with the args tuple. we plan
        // to schedule args_with_notification for deletion after the callback
        // function is completed
        PyTuple_SetItem(args_with_notification, i+1, ref);
    }

    //printf("notification func: %8X, %8X, %8X\n", (unsigned long)func, (unsigned long)args_with_notification, (unsigned long)kwargs);fflush(stdout);
    
    if(kwargs != NULL)
        result = PyObject_Call(func, args_with_notification, kwargs);
    else
    {
        //printf("args_with_notification size: %8X\n", (unsigned long)PyTuple_Size(args_with_notification));
        result = PyObject_CallObject(func, args_with_notification);
        //result = PyObject_CallObject(func, Py_BuildValue("(i)", PyLong_FromLong(1)));
    }

    // done with args_with_notification.  decrement reference pointer
    Py_DECREF(args_with_notification);
    Py_DECREF(pynotification);

    if (result == NULL)
        PyErr_PrintEx(0);
    else
        Py_DECREF(result);

    /*
     * let python know this thread is done modifying and calling it's objects
     */
    PyGILState_Release(gstate);

    //printf("*** OnNotification::lock released\n");fflush(stdout);
}

static PyObject *
manager_Create(PyObject *self, PyObject *args)
{
    OpenZWave::Manager::Create();

    Py_RETURN_NONE;
}

//TODO: implement GetOptions.  requires creating python options class object def

static PyObject *
manager_WriteConfig(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    if (!PyArg_ParseTuple(args, "I", &_homeId))
        return NULL;

    get_manager()->WriteConfig((uint32)_homeId);

    Py_RETURN_NONE;
}

static PyObject *
manager_AddDriver(PyObject *self, PyObject *args)
{
    const char *_controllerPath;
    unsigned int _controllerInterface;
    bool ret;
    if (!PyArg_ParseTuple(args, "sI", &_controllerPath, &_controllerInterface))
        return NULL;

    ret = get_manager()->AddDriver(string(_controllerPath), (OpenZWave::Driver::ControllerInterface)_controllerInterface);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_RemoveDriver(PyObject *self, PyObject *args)
{
    const char *_controllerPath;
    bool ret;
    if (!PyArg_ParseTuple(args, "s", &_controllerPath))
        return NULL;

    ret = get_manager()->RemoveDriver(string(_controllerPath));

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_GetControllerNodeId(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    uint8 ret;
    if (!PyArg_ParseTuple(args, "I", &_homeId))
        return NULL;

    ret = get_manager()->GetControllerNodeId((uint32)_homeId);

    return PyLong_FromUnsignedLong((unsigned long)ret);
}

static PyObject *
manager_IsPrimaryController(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    bool ret;
    if (!PyArg_ParseTuple(args, "I", &_homeId))
        return NULL;

    ret = get_manager()->IsPrimaryController((uint32)_homeId);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_IsStaticUpdateController(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    bool ret;
    if (!PyArg_ParseTuple(args, "I", &_homeId))
        return NULL;

    ret = get_manager()->IsStaticUpdateController((uint32)_homeId);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_IsBridgeController(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    bool ret;
    if (!PyArg_ParseTuple(args, "I", &_homeId))
        return NULL;

    ret = get_manager()->IsBridgeController((uint32)_homeId);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_GetLibraryVersion(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    string ret;
    if (!PyArg_ParseTuple(args, "I", &_homeId))
        return NULL;

    ret = get_manager()->GetLibraryVersion((uint32)_homeId);

    return PyUnicode_FromString(ret.c_str());
}

static PyObject *
manager_GetLibraryTypeName(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    string ret;
    if (!PyArg_ParseTuple(args, "I", &_homeId))
        return NULL;

    ret = get_manager()->GetLibraryTypeName((uint32)_homeId);

    return PyUnicode_FromString(ret.c_str());
}

static PyObject *
manager_GetSendQueueCount(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    int32 ret;
    if (!PyArg_ParseTuple(args, "I", &_homeId))
        return NULL;

    ret = get_manager()->GetSendQueueCount((uint32)_homeId);

    return PyLong_FromUnsignedLong((unsigned long)ret);
}

static PyObject *
manager_SetPollInterval(PyObject *self, PyObject *args)
{
    int _seconds;
    if (!PyArg_ParseTuple(args, "i", &_seconds))
        return NULL;

    get_manager()->SetPollInterval((int32)_seconds);

    Py_RETURN_NONE;
}

static PyObject *
manager_EnablePoll(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    bool ret;
    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->EnablePoll(_id->valueid);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_DisablePoll(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    bool ret;
    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->DisablePoll(_id->valueid);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_isPolled(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    bool ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->isPolled(_id->valueid);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_RefreshNodeInfo(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    bool ret;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->RefreshNodeInfo((uint32)_homeId, (uint8)_nodeId);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_RequestNodeState(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    
    bool ret;
    
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->RequestNodeState((uint32)_homeId, (uint8)_nodeId);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_RequestNodeDynamic(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    
    bool ret;
    
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->RequestNodeDynamic((uint32)_homeId, (uint8)_nodeId);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_IsNodeListeningDevice(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    bool ret;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->IsNodeListeningDevice((uint32)_homeId, (uint8)_nodeId);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_IsNodeRoutingDevice(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    bool ret;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->IsNodeRoutingDevice((uint32)_homeId, (uint8)_nodeId);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_GetNodeMaxBaudRate(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    uint32 ret;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->GetNodeVersion((uint32)_homeId, (uint8)_nodeId);

    return PyLong_FromUnsignedLong((unsigned long)ret);
}

static PyObject *
manager_GetNodeVersion(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    uint8 ret;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->GetNodeVersion((uint32)_homeId, (uint8)_nodeId);

    return PyLong_FromUnsignedLong((unsigned long)ret);
}

static PyObject *
manager_GetNodeSecurity(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    uint8 ret;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->GetNodeSecurity((uint32)_homeId, (uint8)_nodeId);

    return PyLong_FromUnsignedLong((unsigned long)ret);
}

static PyObject *
manager_GetNodeBasic(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    uint8 ret;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->GetNodeBasic((uint32)_homeId, (uint8)_nodeId);

    return PyLong_FromUnsignedLong((unsigned long)ret);
}

static PyObject *
manager_GetNodeGeneric(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    uint8 ret;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->GetNodeGeneric((uint32)_homeId, (uint8)_nodeId);

    return PyLong_FromUnsignedLong((unsigned long)ret);
}

static PyObject *
manager_GetNodeSpecific(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    uint8 ret;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->GetNodeSpecific((uint32)_homeId, (uint8)_nodeId);

    return PyLong_FromUnsignedLong((unsigned long)ret);
}

static PyObject *
manager_GetNodeType(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    string ret;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->GetNodeType((uint32)_homeId, (uint8)_nodeId);

    return PyUnicode_FromString(ret.c_str());
}

static PyObject *
manager_GetNodeNeighbors(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    PyObject *_neighbors;
    uint8* o_neighbors;
    uint32 ret;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->GetNodeNeighbors((uint32)_homeId, (uint8)_nodeId, &o_neighbors);

    if (!ret)
        Py_RETURN_NONE;

    _neighbors = PyList_New(0);
    for(unsigned int i = 0; i < ret; i++)
        PyList_Append(_neighbors, PyLong_FromLong((long)o_neighbors[i]));

    delete o_neighbors;

    return _neighbors;
}

static PyObject *
manager_GetNodeManufacturerName(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    string ret;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->GetNodeManufacturerName((uint32)_homeId, (uint8)_nodeId);

    return PyUnicode_FromString(ret.c_str());
}

static PyObject *
manager_GetNodeProductName(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    string ret;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->GetNodeProductName((uint32)_homeId, (uint8)_nodeId);

    return PyUnicode_FromString(ret.c_str());
}

static PyObject *
manager_GetNodeName(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    string ret;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->GetNodeName((uint32)_homeId, (uint8)_nodeId);

    return PyUnicode_FromString(ret.c_str());
}

static PyObject *
manager_GetNodeLocation(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    string ret;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->GetNodeLocation((uint32)_homeId, (uint8)_nodeId);

    return PyUnicode_FromString(ret.c_str());
}

static PyObject *
manager_SetNodeManufacturerName(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    const char *_manufacturerName;

    if (!PyArg_ParseTuple(args, "IIs", &_homeId, &_nodeId, &_manufacturerName))
        return NULL;

    get_manager()->SetNodeManufacturerName((uint32)_homeId, (uint8)_nodeId, string(_manufacturerName));

    Py_RETURN_NONE;
}

static PyObject *
manager_SetNodeProductName(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    const char *_productName;

    if (!PyArg_ParseTuple(args, "IIs", &_homeId, &_nodeId, &_productName))
        return NULL;

    get_manager()->SetNodeProductName((uint32)_homeId, (uint8)_nodeId, string(_productName));

    Py_RETURN_NONE;
}

static PyObject *
manager_SetNodeName(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    const char *_nodeName;

    if (!PyArg_ParseTuple(args, "IIs", &_homeId, &_nodeId, &_nodeName))
        return NULL;

    get_manager()->SetNodeName((uint32)_homeId, (uint8)_nodeId, string(_nodeName));

    Py_RETURN_NONE;
}

static PyObject *
manager_SetNodeLocation(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    const char *_location;

    if (!PyArg_ParseTuple(args, "IIs", &_homeId, &_nodeId, &_location))
        return NULL;

    get_manager()->SetNodeLocation((uint32)_homeId, (uint8)_nodeId, string(_location));

    Py_RETURN_NONE;
}

static PyObject *
manager_SetNodeOn(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;

    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    get_manager()->SetNodeOn((uint32)_homeId, (uint8)_nodeId);

    Py_RETURN_NONE;
}

static PyObject *
manager_SetNodeOff(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;

    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    get_manager()->SetNodeOff((uint32)_homeId, (uint8)_nodeId);

    Py_RETURN_NONE;
}

static PyObject *
manager_SetNodeLevel(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    unsigned int _level;

    if (!PyArg_ParseTuple(args, "III", &_homeId, &_nodeId, &_level))
        return NULL;

    get_manager()->SetNodeLevel((uint32)_homeId, (uint8)_nodeId, (uint8)_level);
}

static PyObject *
manager_IsNodeInfoReceived(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;

    bool ret;
    
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->IsNodeInfoReceived((uint32)_homeId, (uint8)_nodeId);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
    
}

static PyObject *
manager_GetNodeClassInformation(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    unsigned int _commandClassId;
    string _className;
    uint8 _classVersion;
    PyObject *stringRet;
    PyObject *longRet;
    PyObject *tupRet;
    bool ret;
    
    if (!PyArg_ParseTuple(args, "III", &_homeId, &_nodeId, &_commandClassId))
        return NULL;

    ret = get_manager()->GetNodeClassInformation((uint32)_homeId, (uint8)_nodeId, (uint8)_commandClassId, &_className, &_classVersion);

    if(ret)
    {
        stringRet = PyUnicode_FromString(_className.c_str());
        longRet = PyLong_FromUnsignedLong((unsigned long)_classVersion);
        tupRet = PyTuple_New(2);
        PyTuple_SetItem(tupRet, 0, stringRet);
        PyTuple_SetItem(tupRet, 1, longRet);
        return tupRet;
    }
    else
        Py_RETURN_NONE;
    
}

static PyObject *
manager_GetNodeManufacturerId(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    string ret;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->GetNodeManufacturerId((uint32)_homeId, (uint8)_nodeId);

    return PyUnicode_FromString(ret.c_str());
}

static PyObject *
manager_GetNodeProductType(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    string ret;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->GetNodeProductType((uint32)_homeId, (uint8)_nodeId);

    return PyUnicode_FromString(ret.c_str());
}

static PyObject *
manager_GetNodeProductId(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    string ret;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->GetNodeProductId((uint32)_homeId, (uint8)_nodeId);

    return PyUnicode_FromString(ret.c_str());
}

static PyObject *
manager_GetValueLabel(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    string ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->GetValueLabel(_id->valueid);

    return PyUnicode_FromString(ret.c_str());
}

static PyObject *
manager_SetValueLabel(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    const char *_label;

    if (!PyArg_ParseTuple(args, "Os", &_id, &_label))
        return NULL;

    get_manager()->SetValueLabel(_id->valueid, string(_label));

    Py_RETURN_NONE;
}

static PyObject *
manager_GetValueUnits(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    string ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->GetValueUnits(_id->valueid);

    return PyUnicode_FromString(ret.c_str());
}

static PyObject *
manager_SetValueUnits(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    const char *_units;

    if (!PyArg_ParseTuple(args, "Os", &_id, &_units))
        return NULL;

    get_manager()->SetValueUnits(_id->valueid, string(_units));

    Py_RETURN_NONE;
}

static PyObject *
manager_GetValueMin(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    int32 ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->GetValueMin(_id->valueid);

    return PyLong_FromLong((long)ret);
}

static PyObject *
manager_GetValueMax(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    int32 ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->GetValueMax(_id->valueid);

    return PyLong_FromLong((long)ret);
}

static PyObject *
manager_GetValueHelp(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    string ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->GetValueHelp(_id->valueid);

    return PyUnicode_FromString(ret.c_str());
}

static PyObject *
manager_SetValueHelp(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    const char *_value;

    if (!PyArg_ParseTuple(args, "Os", &_id, &_value))
        return NULL;

    get_manager()->SetValueHelp(_id->valueid, string(_value));

    Py_RETURN_NONE;
}

static PyObject *
manager_IsValueReadOnly(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    bool ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->IsValueReadOnly(_id->valueid);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_IsValueWriteOnly(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    bool ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->IsValueWriteOnly(_id->valueid);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_IsValueSet(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    bool ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->IsValueSet(_id->valueid);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_GetValueAsBool(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    bool o_value;
    bool ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->GetValueAsBool(_id->valueid, &o_value);

    if(ret)
        if(o_value)
            Py_RETURN_TRUE;
        else
            Py_RETURN_FALSE;
    else
        PyErr_SetString(OpenZWaveException, "Either ValueID.GetType() is not defined as Bool, a driver for this value does not exist, or the driver was unable to retrieve the value.");

    // exception occurred
    return NULL;
}

static PyObject *
manager_GetValueAsByte(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    uint8 o_value;
    bool ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->GetValueAsByte(_id->valueid, &o_value);

    if(ret)
        return PyLong_FromUnsignedLong((unsigned long)o_value);
    else
        PyErr_SetString(OpenZWaveException, "Either ValueID.GetType() is not defined as Byte, a driver for this value does not exist, or the driver was unable to retrieve the value.");

    // exception occurred
    return NULL;
}

static PyObject *
manager_GetValueAsFloat(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    float o_value;
    bool ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->GetValueAsFloat(_id->valueid, &o_value);

    if(ret)
        return PyFloat_FromDouble((double)o_value);
    else
        PyErr_SetString(OpenZWaveException, "Either ValueID.GetType() is not defined as Float, a driver for this value does not exist, or the driver was unable to retrieve the value.");

    // exception occurred
    return NULL;
}

static PyObject *
manager_GetValueAsInt(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    int32 o_value;
    bool ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->GetValueAsInt(_id->valueid, &o_value);

    if(ret)
        return PyLong_FromLong((long)o_value);
    else
        PyErr_SetString(OpenZWaveException, "Either ValueID.GetType() is not defined as Int, a driver for this value does not exist, or the driver was unable to retrieve the value.");

    // exception occurred
    return NULL;
}

static PyObject *
manager_GetValueAsShort(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    int16 o_value;
    bool ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->GetValueAsShort(_id->valueid, &o_value);

    if(ret)
        return PyLong_FromUnsignedLong((long)o_value);
    else
        PyErr_SetString(OpenZWaveException, "Either ValueID.GetType() is not defined as Short, a driver for this value does not exist, or the driver was unable to retrieve the value.");

    // exception occurred
    return NULL;
}

static PyObject *
manager_GetValueAsString(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    string o_value;
    bool ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->GetValueAsString(_id->valueid, &o_value);

    if(ret)
        return PyUnicode_FromString(o_value.c_str());
    else
        PyErr_SetString(OpenZWaveException, "A driver for this value does not exist, or the driver was unable to retrieve the value.");

    // exception occurred
    return NULL;
}

static PyObject *
manager_GetValueListSelection(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    string o_value;
    bool ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->GetValueListSelection(_id->valueid, &o_value);

    if(ret)
        return PyUnicode_FromString(o_value.c_str());
    else
        PyErr_SetString(OpenZWaveException, "Either ValueID.GetType() is not defined as int (for list index), a driver for this value does not exist, or the driver was unable to retrieve the value.");

    // exception occurred
    return NULL;
}

static PyObject *
manager_GetValueListSelectionIndex(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    int32 o_value;
    bool ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->GetValueListSelection(_id->valueid, &o_value);

    if(ret)
        return PyLong_FromLong((unsigned long)o_value);
    else
        PyErr_SetString(OpenZWaveException, "Either ValueID.GetType() is not defined as int (for list index), a driver for this value does not exist, or the driver was unable to retrieve the value.");

    // exception occurred
    return NULL;
}

static PyObject *
manager_GetValueListItems(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    vector<string> o_value;
    bool ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->GetValueListItems(_id->valueid, &o_value);

    if(ret)
    {
        PyObject *list_items = PyList_New(0);
        for(unsigned int i = 0; i < o_value.size(); i++)
            PyList_Append(list_items, PyUnicode_FromString(o_value[i].c_str()));
        return list_items;
    }
    else
        PyErr_SetString(OpenZWaveException, "Either ValueID.GetType() is not defined as int (for list index), a driver for this value does not exist, or the driver was unable to retrieve the value.");

    // exception occurred
    return NULL;
}

static PyObject *
manager_SetValueListSelection(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    const char *_selectedItem;
    bool ret;

    if (!PyArg_ParseTuple(args, "Os", &_id, &_selectedItem))
        return NULL;

    ret = get_manager()->SetValueListSelection(_id->valueid, string(_selectedItem));

    if(ret)
        Py_RETURN_NONE;
    else
        PyErr_SetString(OpenZWaveException, "Either ValueID.GetType() is not defined as int (for list index), a driver for this value does not exist, or the driver was unable to set the value (i.e. ReadOnly).");

    // exception occurred
    return NULL;
}

static PyObject *
manager_GetValueFloatPrecision(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    uint8 o_value;
    bool ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->GetValueFloatPrecision(_id->valueid, &o_value);

    if(ret)
        return PyLong_FromLong((unsigned long)o_value);
    else
        PyErr_SetString(OpenZWaveException, "Either ValueID.GetType() is not defined as int (for list index), a driver for this value does not exist, or the driver was unable to retrieve the value.");

    // exception occurred
    return NULL;
}

static PyObject *
manager_PressButton(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    bool ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->PressButton(_id->valueid);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_ReleaseButton(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    bool ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->ReleaseButton(_id->valueid);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_GetNumSwitchPoints(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    uint8 ret;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    ret = get_manager()->GetNumSwitchPoints(_id->valueid);

    return PyLong_FromLong((unsigned long)ret);
}

static PyObject *
manager_SetSwitchPoint(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    unsigned int _hours;
    unsigned int _minutes;
    unsigned int _setback;
    bool ret;

    if (!PyArg_ParseTuple(args, "OIII", &_id, &_hours, &_minutes, &_setback))
        return NULL;

    ret = get_manager()->SetSwitchPoint(_id->valueid, (uint8)_hours, (uint8)_minutes, (int8)_setback);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_RemoveSwitchPoint(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    unsigned int _hours;
    unsigned int _minutes;
    bool ret;

    if (!PyArg_ParseTuple(args, "OII", &_id, &_hours, &_minutes))
        return NULL;

    ret = get_manager()->RemoveSwitchPoint(_id->valueid, (uint8)_hours, (uint8)_minutes);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_ClearSwitchPoints(PyObject *self, PyObject *args)
{
    PyValueID *_id;

    if (!PyArg_ParseTuple(args, "O", &_id))
        return NULL;

    get_manager()->ClearSwitchPoints(_id->valueid);

    Py_RETURN_NONE;
}

static PyObject *
manager_GetSwitchPoint(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    unsigned int _idx;
    uint8 o_hours;
    uint8 o_minutes;
    int8 o_setback;
    PyObject *hoursRet, *minutesRet, *setbackRet;
    PyObject *tupRet;
    bool ret;

    if (!PyArg_ParseTuple(args, "OI", &_id, &_idx))
        return NULL;

    ret = get_manager()->GetSwitchPoint(_id->valueid, (uint8)_idx, &o_hours, &o_minutes, &o_setback);

    if(ret)
    {
        hoursRet = PyLong_FromUnsignedLong((unsigned long)o_hours);
        minutesRet = PyLong_FromUnsignedLong((unsigned long)o_minutes);
        setbackRet = PyLong_FromUnsignedLong((unsigned long)o_setback);
        tupRet = PyTuple_New(3);
        PyTuple_SetItem(tupRet, 0, hoursRet);
        PyTuple_SetItem(tupRet, 1, minutesRet);
        PyTuple_SetItem(tupRet, 2, setbackRet);
        return tupRet;
    }
    else
        Py_RETURN_NONE;
}

static PyObject *
manager_SwitchAllOn(PyObject *self, PyObject *args)
{
    unsigned int _homeId;

    if (!PyArg_ParseTuple(args, "I", &_homeId))
        return NULL;

    get_manager()->SwitchAllOn((uint32)_homeId);

    Py_RETURN_NONE;
}

static PyObject *
manager_SwitchAllOff(PyObject *self, PyObject *args)
{
    unsigned int _homeId;

    if (!PyArg_ParseTuple(args, "I", &_homeId))
        return NULL;

    get_manager()->SwitchAllOff((uint32)_homeId);

    Py_RETURN_NONE;
}

/*
 * Python does not have multiple prototypes for functions.  Therefore there can
 * only be one SetValue.  It needs to be smart and figure out which type was
 * passed as the input, and call the appropriate SetValue function.
 */
static PyObject *
manager_SetValue(PyObject *self, PyObject *args)
{
    PyValueID *_id;
    PyObject *_input;
    bool ret = false;
    PyObject *bytesAscii;
    
    if (!PyArg_ParseTuple(args, "OO", &_id, &_input))
        return NULL;

    //fflush(stdout);printf("Entering SetValue Routine\n");fflush(stdout);
    
    if(PyBool_Check(_input))
    {
        //fflush(stdout);printf("Got Bool Type\n");fflush(stdout);
        bool value;
        if (_id->valueid.GetType() != ValueID::ValueType_Bool)
        {
            PyErr_SetString(OpenZWaveException, "Python Bool passed as input to Manager.SetValue() except that associated value is not defined as a boolean.");
            return NULL;
        }

        if(_input == Py_False)
            value = false;
        else
            value = true;

        ret = get_manager()->SetValue(_id->valueid, value);
    }
    else if (PyLong_Check(_input))
    {
        //fflush(stdout);printf("Got Long Type\n");fflush(stdout);
        switch ( _id->valueid.GetType() )
        {
            case ValueID::ValueType_Byte:
            {
                ret = get_manager()->SetValue(_id->valueid, (uint8)PyLong_AsUnsignedLong(_input));
                break;
            }
            case ValueID::ValueType_Int:
            {
                ret = get_manager()->SetValue(_id->valueid, (int32)PyLong_AsUnsignedLong(_input));
                break;
            }
            case ValueID::ValueType_Short:
            {
                ret = get_manager()->SetValue(_id->valueid, (int16)PyLong_AsUnsignedLong(_input));
                break;
            }
            default:
            {
                PyErr_SetString(OpenZWaveException, "Python Long passed as input to Manager.SetValue() except that associated value is not defined as a byte, short, or int.");
                return NULL;
            }
        }
    }
    else if(PyFloat_Check(_input))
    {
        //fflush(stdout);printf("Got Float Type\n");fflush(stdout);
        if (_id->valueid.GetType() != ValueID::ValueType_Decimal)
        {
            PyErr_SetString(OpenZWaveException, "Python Float passed as input to Manager.SetValue() except that associated value is not defined as a decimal/float/double value.");
            return NULL;
        }
        ret = get_manager()->SetValue(_id->valueid, (float)PyFloat_AsDouble(_input));
    }
    else if (PyUnicode_Check(_input))
    {
        //fflush(stdout);printf("Got Unicode Type\n");fflush(stdout);
        char *errors;
        bytesAscii = PyUnicode_Encode(PyUnicode_AsUnicode(_input), PyUnicode_GetSize(_input), "ascii", errors);
        char * buffer = PyBytes_AsString(bytesAscii);
        //PyObject_Print(_input, stdout, 0); fflush(stdout);
        //fflush(stdout);printf("SetValue(%s)\n", buffer);fflush(stdout);
        ret = get_manager()->SetValue(_id->valueid, (string)buffer);
        //fflush(stdout);printf("Done with SetValue(String)\n");fflush(stdout);
        Py_XDECREF(bytesAscii);
        
    }
    //else if (PyString_Check(_input))
    //{
    //    char * buffer = PyBytes_AsString(_input);
    //    ret = get_manager()->SetValue(_id->valueid, (string)buffer);
    //}

    //fflush(stdout);printf("ret: %d\n", ret);fflush(stdout);
    
    if(ret)
        Py_RETURN_NONE;
    else
        PyErr_SetString(OpenZWaveException, "Either ValueID.GetType() is not defined as int (for list index), a driver for this value does not exist, or the driver was unable to set the value (i.e. ReadOnly).");

    // exception occurred
    return NULL;
}

static PyObject *
manager_SetConfigParam(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    unsigned int _param;
    unsigned int _size;
    
    int _value;
    bool ret;
    if (!PyArg_ParseTuple(args, "IIIiI", &_homeId, &_nodeId, &_param, &_value, &_size))
        return NULL;

    ret = get_manager()->SetConfigParam((uint32)_homeId, (uint8)_nodeId, (uint8)_param, (int32)_value, (uint8)_size);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_RequestConfigParam(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    unsigned int _param;
    if (!PyArg_ParseTuple(args, "III", &_homeId, &_nodeId, &_param))
        return NULL;

    get_manager()->RequestConfigParam((uint32)_homeId, (uint8)_nodeId, (uint8)_param);

    Py_RETURN_NONE;
}

static PyObject *
manager_RequestAllConfigParams(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    get_manager()->RequestAllConfigParams((uint32)_homeId, (uint8)_nodeId);

    Py_RETURN_NONE;
}

static PyObject *
manager_GetNumGroups(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    uint8 ret;
    if (!PyArg_ParseTuple(args, "II", &_homeId, &_nodeId))
        return NULL;

    ret = get_manager()->GetNumGroups((uint32)_homeId, (uint8)_nodeId);

    return PyLong_FromLong((unsigned long)ret);
}

static PyObject *
manager_GetAssociations(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    unsigned int _groupIdx;
    PyObject *_associations;
    uint8* o_associations;
    uint32 ret;
    if (!PyArg_ParseTuple(args, "III", &_homeId, &_nodeId, &_groupIdx))
        return NULL;

    ret = get_manager()->GetAssociations((uint32)_homeId, (uint8)_nodeId, (uint8)_groupIdx, &o_associations);
    //"returned
    //printf("**** returned %d associations!\n", ret);fflush(stdout);

    if (!ret)
        Py_RETURN_NONE;

    _associations = PyList_New(0);
    for(unsigned int i = 0; i < ret; i++)
        PyList_Append(_associations, PyLong_FromLong((long)o_associations[i]));

    delete o_associations;

    return _associations;
}

static PyObject *
manager_GetMaxAssociations(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    unsigned int _groupIdx;
    uint8 num_associations;

    if (!PyArg_ParseTuple(args, "III", &_homeId, &_nodeId, &_groupIdx))
        return NULL;

    num_associations = get_manager()->GetMaxAssociations((uint32)_homeId, (uint8)_nodeId, (uint8)_groupIdx);

    return PyLong_FromLong((unsigned long)num_associations);
}

static PyObject *
manager_GetGroupLabel(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    unsigned int _groupIdx;
    string group_label;

    if (!PyArg_ParseTuple(args, "III", &_homeId, &_nodeId, &_groupIdx))
        return NULL;

    group_label = get_manager()->GetGroupLabel((uint32)_homeId, (uint8)_nodeId, (uint8)_groupIdx);

    return PyUnicode_FromString(group_label.c_str());
}

static PyObject *
manager_AddAssociation(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    unsigned int _groupIdx;
    unsigned int _targetNodeId;
    if (!PyArg_ParseTuple(args, "IIII", &_homeId, &_nodeId, &_groupIdx, &_targetNodeId))
        return NULL;

    get_manager()->AddAssociation((uint32)_homeId, (uint8)_nodeId, (uint8)_groupIdx, (uint8)_targetNodeId);

    Py_RETURN_NONE;
}

static PyObject *
manager_RemoveAssociation(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    unsigned int _nodeId;
    unsigned int _groupIdx;
    unsigned int _targetNodeId;
    if (!PyArg_ParseTuple(args, "IIII", &_homeId, &_nodeId, &_groupIdx, &_targetNodeId))
        return NULL;

    get_manager()->RemoveAssociation((uint32)_homeId, (uint8)_nodeId, (uint8)_groupIdx, (uint8)_targetNodeId);

    Py_RETURN_NONE;
}

/*
 * the interface of this function, due to c to python translation, accepts a
 * python function as the first arg, an indefinite number of positional args
 * after that, and then an indefinite number of kwargs after that.  this allows
 * the user to specify any python function/method to be used for a callback,
 * providing argument and keyword argument values on the fly.
 *
 * because this function has to assemble a new "context" based on the func,
 * args, and kwargs, it also needs to do a rich comparison to see if a watcher
 * has already been added with the exact same args and kwargs.  because
 * openzwave does not make m_watchers public, this module will need to maintain
 * it's own list.
 */
static PyObject *
manager_AddWatcher(PyObject *self, PyObject *args, PyObject *kwargs)
{
    PyObject *func, *args_without_func;
    PyCallbackObjects *callback_objects;
    list<PyCallbackObjects*>::iterator it;
    bool ret = false;
    bool richcompare = false;

    // python function object should be first arg
    func = PyTuple_GetItem(args, 0);

    if(!PyCallable_Check(func))
    {
        PyErr_SetString(OpenZWaveException, "First argument to manager.AddWatcher must be callable");
        return NULL;
    }

    // get a splice of the rest of the positional arguments.  basically, discard
    // the first argument
    args_without_func = PyTuple_GetSlice(args, 1, PyTuple_Size(args));
    if(args_without_func == NULL)
        return NULL;

    // perform rich comparison to see if func, args, kwargs are already in the
    // watchers list
    for( it = watchers.begin(); it != watchers.end(); ++it )
    {
        // default to compare successful.  any miscompare will reset this flag
        // to false.
        richcompare = true;

        // it.func == func?
        if(PyObject_RichCompareBool((*it)->func, func, Py_EQ) == 0)
            richcompare = false;

        // it.args == args_without_func?
        if(PyObject_RichCompareBool((*it)->args, args_without_func, Py_EQ) == 0)
            richcompare = false;

        // it.kwargs == kwargs
        if(kwargs != NULL && (*it)->kwargs != NULL && PyObject_RichCompareBool((*it)->kwargs, kwargs, Py_EQ) == 0)
            richcompare = false;

        // did the compare succeed on the last iteration, if so, break out of
        // the loop with the current iterator pointer at the matched record
        if(richcompare)
            break;

    }

    if(richcompare)
    {
        // "it" points to matching notification objects, which has already been
        // instantiated and had it's references incremented.
        callback_objects = *it;
        //printf("notification func: %8X\n", (unsigned long)(*it)->func);fflush(stdout);
        //printf("%8X, %8X", (unsigned long)callback_objects->func, (unsigned long)func);
        //PyObject_Print(callback_objects->kwargs, stdout, 0); fflush(stdout);
    }
    else
    {
        // no func,args,kwargs have been added as watchers yet.  assemble a new
        // one
        callback_objects = new PyCallbackObjects;
        callback_objects->func = func;
        callback_objects->args = args_without_func;
        callback_objects->kwargs = kwargs;

        // not necessary to Py_INCREF(args_without_func), GetSlice returns a
        // new reference
        Py_INCREF(func);
        Py_XINCREF(kwargs); // can be NULL if no kwargs provided, so use XINCREF

    }

    //printf("calling onnotification \n");fflush(stdout);
    //OnNotification(NULL, (void*)callback_objects);
    //printf("calling addwatcher \n");fflush(stdout);
    ret = get_manager()->AddWatcher(OnNotification, (void*)callback_objects);
    if(!richcompare) watchers.push_back(callback_objects);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_RemoveWatcher(PyObject *self, PyObject *args, PyObject *kwargs)
{
    PyObject *func, *args_without_func;
    PyCallbackObjects *callback_objects;
    list<PyCallbackObjects*>::iterator it;
    bool ret = false;
    bool richcompare = false;

    // python function object should be first arg
    func = PyTuple_GetItem(args, 0);

    if(!PyCallable_Check(func))
    {
        PyErr_SetString(OpenZWaveException, "First argument to manager.AddWatcher must be callable");
        return NULL;
    }

    // get a splice of the rest of the positional arguments.  basically, discard
    // the first argument
    args_without_func = PyTuple_GetSlice(args, 1, PyTuple_Size(args));
    if(args_without_func == NULL)
        return NULL;

    // perform rich comparison to see if func, args, kwargs are already in the
    // watchers list
    for( it = watchers.begin(); it != watchers.end(); ++it )
    {
        // default to compare successful.  any miscompare will reset this flag
        // to false.
        richcompare = true;

        // it.func == func?
        if(PyObject_RichCompareBool((*it)->func, func, Py_EQ) == 0)
            richcompare = false;

        // it.args == args_without_func?
        if(PyObject_RichCompareBool((*it)->args, args_without_func, Py_EQ) == 0)
            richcompare = false;

        // it.kwargs == kwargs
        if(kwargs != NULL && (*it)->kwargs != NULL && PyObject_RichCompareBool((*it)->kwargs, kwargs, Py_EQ) == 0)
            richcompare = false;

        // did the compare succeed on the last iteration, if so, break out of
        // the loop with the current iterator pointer at the matched record
        if(richcompare)
            break;

    }

    if(!richcompare)
        Py_RETURN_FALSE;

    callback_objects = *it;

    // RemoveWatcher will delete callback_objects, but we first need to decref
    // the python objects it points to, in addition to objects used in this
    // local scope.
    Py_DECREF(args_without_func);
    Py_DECREF(callback_objects->func);
    Py_DECREF(callback_objects->args);
    Py_XDECREF(callback_objects->kwargs);

    ret = get_manager()->RemoveWatcher(OnNotification, (void*)callback_objects);

    if(ret)
    {
        // remove our own reference to callback object
        watchers.erase(it);
        Py_RETURN_TRUE;
    }
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_ResetController(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    if (!PyArg_ParseTuple(args, "I", &_homeId))
        return NULL;

    get_manager()->ResetController((uint32)_homeId);

    Py_RETURN_NONE;
}

static PyObject *
manager_SoftReset(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    if (!PyArg_ParseTuple(args, "I", &_homeId))
        return NULL;

    get_manager()->SoftReset((uint32)_homeId);

    Py_RETURN_NONE;
}

static PyObject *
manager_BeginControllerCommand(PyObject *self, PyObject *args, PyObject *kwargs)
{
    PyObject *func, *args_without_func, *current_arg;
    PyCallbackObjects *callback_objects;
    Driver::ControllerCommand _command = Driver::ControllerCommand_None;
    uint32 _homeId = NULL;
    uint32 _nodeId;

    bool _highPower;
    bool ret;
    bool argcheck_failed = false;

    // parse _homeId
    current_arg = PyTuple_GetItem(args, 0);
    if(PyLong_Check(current_arg) || PyLong_Check(current_arg))
        _homeId = (uint32)PyLong_AsLong(current_arg);
    else
        argcheck_failed = true;

    // parse _command
    current_arg = PyTuple_GetItem(args, 1);
    if(PyLong_Check(current_arg) || PyLong_Check(current_arg))
        _command = (Driver::ControllerCommand)PyLong_AsLong(current_arg);
    else
        argcheck_failed = true;

    // parse _highPower
    current_arg = PyTuple_GetItem(args, 2);
    if(PyBool_Check(current_arg))
        _highPower = (current_arg == Py_True ? true : false);
    else
        argcheck_failed = true;

    // parse _nodeId
    current_arg = PyTuple_GetItem(args, 3);
    if(PyLong_Check(current_arg) || PyLong_Check(current_arg))
        _nodeId = (uint32)PyLong_AsLong(current_arg);
    else
        argcheck_failed = true;

    // parse callback func
    current_arg = PyTuple_GetItem(args, 4);
    if(argcheck_failed == false && PyCallable_Check(current_arg))
    {
        func = current_arg;
        Py_INCREF(func);
    }
    else
        argcheck_failed = true;

    if (argcheck_failed)
    {
        PyErr_SetString(OpenZWaveException, "Invalid arguments to manager.BeginControllerCommand(_homeId, _command, _highPower, _nodeId, pycallback, *args, **kwargs)");
        return NULL;
    }

    args_without_func = PyTuple_GetSlice(args, 5, PyTuple_Size(args));
    Py_XINCREF(kwargs);
    callback_objects = new PyCallbackObjects;
    callback_objects->func = func;
    callback_objects->args = args_without_func;
    callback_objects->kwargs = kwargs;
    callback_objects->_homeId = _homeId;
    
    ongoing_commands[(void*)callback_objects] = callback_objects;
    
    ret = get_manager()->BeginControllerCommand(_homeId, _command, OnControllerCommand, (void*)callback_objects, _highPower, _nodeId);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyObject *
manager_CancelControllerCommand(PyObject *self, PyObject *args)
{
    unsigned int _homeId;
    bool ret;
    if (!PyArg_ParseTuple(args, "I", &_homeId))
        return NULL;

    ret = get_manager()->CancelControllerCommand((uint32)_homeId);

    if(ret)
        Py_RETURN_TRUE;
    else
        Py_RETURN_FALSE;
}

static PyMethodDef manager_Methods[] = {

    {"Create",  manager_Create, METH_VARARGS,
     "Instantiate built-in singleton Manager class."},

    {"WriteConfig",  manager_WriteConfig, METH_VARARGS,
     "Save the configuration of a driver to a file."},

    {"AddDriver",  manager_AddDriver, METH_VARARGS,
     "Add a new Z-Wave PC Interface."},

    {"RemoveDriver",  manager_RemoveDriver, METH_VARARGS,
     "Remove a Z-Wave PC Interface."},

    {"GetControllerNodeId",  manager_GetControllerNodeId, METH_VARARGS,
     "Get the node ID of the Z-Wave controller.."},

    {"IsPrimaryController",  manager_IsPrimaryController, METH_VARARGS,
     "Return boolean indicating primary controller status."},

    {"IsStaticUpdateController",  manager_IsStaticUpdateController, METH_VARARGS,
     "Query if the controller is a static update controller."},

    {"IsBridgeController",  manager_IsBridgeController, METH_VARARGS,
     "Query if the controller is using the bridge controller library."},

    {"GetLibraryVersion",  manager_GetLibraryVersion, METH_VARARGS,
     "Get the version of the Z-Wave API library used by a controller."},

    {"GetLibraryTypeName",  manager_GetLibraryTypeName, METH_VARARGS,
     "Get a string containing the Z-Wave API library type used by a controller."},

    {"GetSendQueueCount",  manager_GetSendQueueCount, METH_VARARGS,
     "Get count of messages in the outgoing send queue."},

    {"SetPollInterval",  manager_SetPollInterval, METH_VARARGS,
     "Set the polling interval on all drivers."},

    {"EnablePoll",  manager_EnablePoll, METH_VARARGS,
     "Enable polling of a value."},

    {"DisablePoll",  manager_DisablePoll, METH_VARARGS,
     "Disable polling of a value."},

    {"isPolled",  manager_isPolled, METH_VARARGS,
     "Determine the polling of a device's state."},

    {"RefreshNodeInfo",  manager_RefreshNodeInfo, METH_VARARGS,
     "Fetch the data for a node from the Z-Wave network."},

    {"RequestNodeState",  manager_RequestNodeState, METH_VARARGS,
     "Fetch the command class data for a node from the Z-Wave network."},

    {"RequestNodeDynamic",  manager_RequestNodeDynamic, METH_VARARGS,
     "Trigger the fetching of just the dynamic value data for a node."},

    {"IsNodeListeningDevice",  manager_IsNodeListeningDevice, METH_VARARGS,
     "Get whether the node is a listening device that does not go to sleep."},

    {"IsNodeRoutingDevice",  manager_IsNodeRoutingDevice, METH_VARARGS,
     "Get whether the node is a routing device that passes messages to other nodes."},

    {"GetNodeMaxBaudRate",  manager_GetNodeMaxBaudRate, METH_VARARGS,
     "Get the maximum baud rate of a node's communications."},

    {"GetNodeVersion",  manager_GetNodeVersion, METH_VARARGS,
     "Get the version number of a node."},

    {"GetNodeSecurity",  manager_GetNodeSecurity, METH_VARARGS,
     "Get the security byte for a node (bit meanings still to be determined)."},

    {"GetNodeBasic",  manager_GetNodeBasic, METH_VARARGS,
     "Get the basic type of a node."},

    {"GetNodeGeneric",  manager_GetNodeGeneric, METH_VARARGS,
     "Get the generic type of a node."},

    {"GetNodeSpecific",  manager_GetNodeSpecific, METH_VARARGS,
     "Get the specific type of a node."},

    {"GetNodeType",  manager_GetNodeType, METH_VARARGS,
     "Get a string describing the type of a node."},

    {"GetNodeNeighbors",  manager_GetNodeNeighbors, METH_VARARGS,
     "Get the bitmap of this node's neighbors."},

    {"GetNodeManufacturerName",  manager_GetNodeManufacturerName, METH_VARARGS,
     "Get the manufacturer name of a node."},

    {"GetNodeProductName",  manager_GetNodeProductName, METH_VARARGS,
     "Get the product name of a node."},

    {"GetNodeName",  manager_GetNodeName, METH_VARARGS,
     "Get the user-editable name of a node."},

    {"GetNodeLocation",  manager_GetNodeLocation, METH_VARARGS,
     "Get the location of a node."},

    {"SetNodeManufacturerName",  manager_SetNodeManufacturerName, METH_VARARGS,
     "Set the manufacturer name a node."},

    {"SetNodeProductName",  manager_SetNodeProductName, METH_VARARGS,
     "Set the product name of a node."},

    {"SetNodeName",  manager_SetNodeName, METH_VARARGS,
     "Set the node name value with the specified ID."},

    {"SetNodeLocation",  manager_SetNodeLocation, METH_VARARGS,
     "Set a string describing the location of a node."},

    {"SetNodeOn",  manager_SetNodeOn, METH_VARARGS,
     "Turns a node on."},

    {"SetNodeOff",  manager_SetNodeOff, METH_VARARGS,
     "Turns a node off."},

    {"SetNodeLevel",  manager_SetNodeLevel, METH_VARARGS,
     "Sets the basic level of a node."},

    {"IsNodeInfoReceived",  manager_IsNodeInfoReceived, METH_VARARGS,
     "Get whether the node information has been received."},

    {"GetNodeClassInformation",  manager_GetNodeClassInformation, METH_VARARGS,
     "Get whether the node has the defined class available or not."},

    {"GetNodeManufacturerId",  manager_GetNodeManufacturerId, METH_VARARGS,
     "Get the manufacturer ID value of a node."},

    {"GetNodeProductType",  manager_GetNodeProductType, METH_VARARGS,
     "Get the product type value of a node."},

    {"GetNodeProductId",  manager_GetNodeProductId, METH_VARARGS,
     "Get the product Id value with the specified ID."},

    {"GetValueLabel",  manager_GetValueLabel, METH_VARARGS,
     "Gets the user-friendly label for the value."},

    {"SetValueLabel",  manager_SetValueLabel, METH_VARARGS,
     "Sets the user-friendly label for the value."},

    {"GetValueUnits",  manager_GetValueUnits, METH_VARARGS,
     "Gets the units that the value is measured in."},

    {"SetValueUnits",  manager_SetValueUnits, METH_VARARGS,
     "Sets the units that the value is measured in."},

    {"GetValueMin",  manager_GetValueMin, METH_VARARGS,
     "Gets the minimum that this value may contain."},

    {"GetValueMax",  manager_GetValueMax, METH_VARARGS,
     "Gets the maximum that this value may contain."},

    {"GetValueHelp",  manager_GetValueHelp, METH_VARARGS,
     "Gets a help string describing the value's purpose and usage."},

    {"SetValueHelp",  manager_SetValueHelp, METH_VARARGS,
     "Sets a help string describing the value's purpose and usage."},

    {"IsValueReadOnly",  manager_IsValueReadOnly, METH_VARARGS,
     "Test whether the value is read-only."},

    {"IsValueWriteOnly",  manager_IsValueWriteOnly, METH_VARARGS,
     "Test whether the value is write-only."},

    {"IsValueSet",  manager_IsValueSet, METH_VARARGS,
     "Test whether the value has been set by a status message from the device."},

    {"GetValueAsBool",  manager_GetValueAsBool, METH_VARARGS,
     "Gets a value as a bool."},

    {"GetValueAsByte",  manager_GetValueAsByte, METH_VARARGS,
     "Gets a value as an 8-bit unsigned integer."},

    {"GetValueAsFloat",  manager_GetValueAsFloat, METH_VARARGS,
     "Gets a value as a floating point number."},

    {"GetValueAsInt",  manager_GetValueAsInt, METH_VARARGS,
     "Gets a value as a 32-bit signed integer."},

    {"GetValueAsShort",  manager_GetValueAsShort, METH_VARARGS,
     "Gets a value as a 16-bit signed integer."},

    {"GetValueAsString",  manager_GetValueAsString, METH_VARARGS,
     "Creates a string representation of the value, regardless of type."},

    {"GetValueListSelection",  manager_GetValueListSelection, METH_VARARGS,
     "Gets the selected item from a list value."},

    {"GetValueListSelectionIndex",  manager_GetValueListSelectionIndex, METH_VARARGS,
     "Gets the selected item index from a list value."},

    {"GetValueListItems",  manager_GetValueListItems, METH_VARARGS,
     "Gets the list of items from a list value."},

    {"SetValue",  manager_SetValue, METH_VARARGS,
     "Sets the value from a python object."},

    {"SetValueListSelection",  manager_SetValueListSelection, METH_VARARGS,
     "Sets the value list from a python object."},

    {"GetValueFloatPrecision",  manager_GetValueFloatPrecision, METH_VARARGS,
     "Gets a float value's precision."},

    {"PressButton",  manager_PressButton, METH_VARARGS,
     "Starts an activity in a device."},

    {"ReleaseButton",  manager_ReleaseButton, METH_VARARGS,
     "Stops an activity in a device."},

    {"GetNumSwitchPoints",  manager_GetNumSwitchPoints, METH_VARARGS,
     "Get the number of switch points defined in a schedule."},

    {"SetSwitchPoint",  manager_SetSwitchPoint, METH_VARARGS,
     "Set a switch point in the schedule."},

    {"RemoveSwitchPoint",  manager_RemoveSwitchPoint, METH_VARARGS,
     "Remove a switch point from the schedule."},

    {"ClearSwitchPoint",  manager_ClearSwitchPoints, METH_VARARGS,
     "Clears all switch points from the schedule."},

    {"GetSwitchPoint",  manager_GetSwitchPoint, METH_VARARGS,
     "Gets switch point data from the schedule."},

    {"SwitchAllOn",  manager_SwitchAllOn, METH_VARARGS,
     "Switch all devices on."},

    {"SwitchAllOff",  manager_SwitchAllOff, METH_VARARGS,
     "Switch all devices off."},

    {"SetConfigParam",  manager_SetConfigParam, METH_VARARGS,
     "Set the value of one of the configuration parameters of a device."},

    {"RequestConfigParam",  manager_RequestConfigParam, METH_VARARGS,
     "Request the value of one of the configuration parameters of a device."},

    {"RequestAllConfigParams",  manager_RequestAllConfigParams, METH_VARARGS,
     "Request the values of all known configurable parameters from a device."},

    {"GetNumGroups",  manager_GetNumGroups, METH_VARARGS,
     "Gets the number of association groups reported by this node."},

    {"GetAssociations",  manager_GetAssociations, METH_VARARGS,
     "Gets the associations for a group."},

    {"GetMaxAssociations",  manager_GetMaxAssociations, METH_VARARGS,
     "Gets the maximum number of associations for a group."},

    {"GetGroupLabel",  manager_GetGroupLabel, METH_VARARGS,
     "Returns a label for the particular group of a node."},

    {"AddAssociation",  manager_AddAssociation, METH_VARARGS,
     "Adds a node to an association group."},

    {"RemoveAssociation",  manager_RemoveAssociation, METH_VARARGS,
     "Removes a node from an association group."},

    {"AddWatcher",  (PyCFunction)((PyCFunctionWithKeywords)manager_AddWatcher), METH_VARARGS | METH_KEYWORDS,
     "Add callback function when an internal notification event occurs.  First argument of function must be reserved for a pyopenzwave.Notification object."},

    {"RemoveWatcher",  (PyCFunction)((PyCFunctionWithKeywords)manager_RemoveWatcher), METH_VARARGS | METH_KEYWORDS,
      "Remove callback function for when an internal notification event occurs.  Must pass same func, *args, **kwargs that were passed to AddWatcher."},

    {"ResetController",  manager_ResetController, METH_VARARGS,
     "Reset controller and erase all node information."},

    {"SoftReset",  manager_SoftReset, METH_VARARGS,
     "Soft-reset the Z-Wave controller chip."},

    {"BeginControllerCommand",  (PyCFunction)((PyCFunctionWithKeywords)manager_BeginControllerCommand), METH_VARARGS | METH_KEYWORDS,
      "Start the controller performing one of its network management functions. BeginControllerCommand(_homeId, _command, _highPower, func, *args, **kwargs)."},

    {"CancelControllerCommand",  manager_CancelControllerCommand, METH_VARARGS,
     "Stop the current controller function."},

    {NULL, NULL, 0, NULL}
};

// TODO: implement scene functions, GetDriverStatistics
 
// pyopenzwave.manager
///////////////////////////////////////////////////////////////////////////////

static PyModuleDef pyopenzwavemodule = {
    PyModuleDef_HEAD_INIT,
    "pyopenzwave",
    "Python 3+ wrapper for the OpenZWave C++ library.",
    -1,
    pyopenzwave_Methods, NULL, NULL, NULL, NULL
};

static PyModuleDef managermodule = {
    PyModuleDef_HEAD_INIT,
    "manager",
    "Contains all the singleton manager methods from OpenZWave.",
    -1,
    manager_Methods, NULL, NULL, NULL, NULL
};

static PyModuleDef optionsmodule = {
    PyModuleDef_HEAD_INIT,
    "options",
    "Contains all the singleton option methods from OpenZWave.",
    -1,
    options_Methods, NULL, NULL, NULL, NULL
};

PyMODINIT_FUNC
PyInit_pyopenzwave()
{
    PyObject *manager, *options, *pyopenzwave;

    if(PyType_Ready(&PyValueIDType) < 0)
        return NULL;

    if(PyType_Ready(&PyDriverType) < 0)
        return NULL;

    if(PyType_Ready(&PyNotificationType) < 0)
        return NULL;

    manager = PyModule_Create(&managermodule);
    options = PyModule_Create(&optionsmodule);
    pyopenzwave = PyModule_Create(&pyopenzwavemodule);

    PyModule_AddObject(pyopenzwave, "manager", manager);

    PyModule_AddObject(pyopenzwave, "options", options);

    Py_INCREF(&PyValueIDType);
    PyModule_AddObject(pyopenzwave, "ValueID", (PyObject *)&PyValueIDType);

    Py_INCREF(&PyDriverType);
    PyModule_AddObject(pyopenzwave, "Driver", (PyObject *)&PyDriverType);

    Py_INCREF(&PyNotificationType);
    PyModule_AddObject(pyopenzwave, "Notification", (PyObject *)&PyNotificationType);

    OpenZWaveException = PyErr_NewException((const char *)"pyopenzwave.OpenZWaveException", NULL, NULL);
    Py_INCREF(OpenZWaveException);
    PyModule_AddObject(pyopenzwave, "OpenZWaveException", OpenZWaveException);

    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_ValueAdded",       PyLong_FromLong(Notification::Type_ValueAdded));
    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_ValueRemoved",     PyLong_FromLong(Notification::Type_ValueRemoved));
    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_ValueChanged",     PyLong_FromLong(Notification::Type_ValueChanged));
    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_Group",            PyLong_FromLong(Notification::Type_Group));
    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_NodeNew",          PyLong_FromLong(Notification::Type_NodeNew));
    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_NodeAdded",        PyLong_FromLong(Notification::Type_NodeAdded));
    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_NodeRemoved",      PyLong_FromLong(Notification::Type_NodeRemoved));
    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_NodeProtocolInfo", PyLong_FromLong(Notification::Type_NodeProtocolInfo));
    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_NodeNaming",       PyLong_FromLong(Notification::Type_NodeNaming));
    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_NodeEvent",        PyLong_FromLong(Notification::Type_NodeEvent));
    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_PollingDisabled",  PyLong_FromLong(Notification::Type_PollingDisabled));
    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_PollingEnabled",   PyLong_FromLong(Notification::Type_PollingEnabled));
    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_DriverReady",      PyLong_FromLong(Notification::Type_DriverReady));
    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_DriverFailed",      PyLong_FromLong(Notification::Type_DriverFailed));
    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_DriverReset",      PyLong_FromLong(Notification::Type_DriverReset));
    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_MsgComplete",      PyLong_FromLong(Notification::Type_MsgComplete));
    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_EssentialNodeQueriesComplete",PyLong_FromLong(Notification::Type_EssentialNodeQueriesComplete));
    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_NodeQueriesComplete",PyLong_FromLong(Notification::Type_NodeQueriesComplete));
    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_AwakeNodesQueried",PyLong_FromLong(Notification::Type_AwakeNodesQueried));
    PyDict_SetItemString(PyNotificationType.tp_dict, "Type_AllNodesQueried",  PyLong_FromLong(Notification::Type_AllNodesQueried));

    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerState_Normal",      PyLong_FromLong(Driver::ControllerState_Normal));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerState_Waiting",     PyLong_FromLong(Driver::ControllerState_Waiting));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerState_InProgress",  PyLong_FromLong(Driver::ControllerState_InProgress));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerState_Completed",   PyLong_FromLong(Driver::ControllerState_Completed));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerState_Failed",      PyLong_FromLong(Driver::ControllerState_Failed));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerState_NodeOK",      PyLong_FromLong(Driver::ControllerState_NodeOK));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerState_NodeFailed",  PyLong_FromLong(Driver::ControllerState_NodeFailed));

    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerInterface_Serial",  PyLong_FromLong(Driver::ControllerInterface_Serial));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerInterface_Hid",     PyLong_FromLong(Driver::ControllerInterface_Hid));

    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerCommand_None",                 PyLong_FromLong(Driver::ControllerCommand_None));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerCommand_AddController",        PyLong_FromLong(Driver::ControllerCommand_AddController));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerCommand_AddDevice",            PyLong_FromLong(Driver::ControllerCommand_AddDevice));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerCommand_CreateNewPrimary",     PyLong_FromLong(Driver::ControllerCommand_CreateNewPrimary));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerCommand_ReceiveConfiguration", PyLong_FromLong(Driver::ControllerCommand_ReceiveConfiguration));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerCommand_RemoveController",     PyLong_FromLong(Driver::ControllerCommand_RemoveController));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerCommand_RemoveDevice",         PyLong_FromLong(Driver::ControllerCommand_RemoveDevice));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerCommand_RemoveFailedNode",     PyLong_FromLong(Driver::ControllerCommand_RemoveFailedNode));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerCommand_HasNodeFailed",        PyLong_FromLong(Driver::ControllerCommand_HasNodeFailed));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerCommand_ReplaceFailedNode",    PyLong_FromLong(Driver::ControllerCommand_ReplaceFailedNode));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerCommand_TransferPrimaryRole",  PyLong_FromLong(Driver::ControllerCommand_TransferPrimaryRole));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerCommand_RequestNetworkUpdate", PyLong_FromLong(Driver::ControllerCommand_RequestNetworkUpdate));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerCommand_RequestNodeNeighborUpdate",         PyLong_FromLong(Driver::ControllerCommand_RequestNodeNeighborUpdate));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerCommand_AssignReturnRoute",    PyLong_FromLong(Driver::ControllerCommand_AssignReturnRoute));
    PyDict_SetItemString(PyDriverType.tp_dict, "ControllerCommand_DeleteAllReturnRoutes",PyLong_FromLong(Driver::ControllerCommand_DeleteAllReturnRoutes));
    
    /*
     * this is a crucial routine to call for openzwave.  it tells python that
     * this is a multi-threaded application and that it needs to initialize its
     * global interpreter lock and other variables so that we can hand-shake
     * with our threads later.
     */
    PyEval_InitThreads();

    return pyopenzwave;
}


